#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>

#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>


#define DEMO_DRIVER_NAME "demo_platform_driver"
#define DEMO_CDEV_NAME "demo_cdev"
#define DEMO_CLASS_NAME "demo_class"
#define DEMO_DEVICE_NAME "demo_device"

static char buffer[1024];
static int cdev_major = 0;
static struct class *demo_class = NULL;
static struct device *demo_device = NULL;

static int demo_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "Device opened\n");
	return 0;
}

static int demo_release(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "Device closed\n");
	return 0;
}

static ssize_t demo_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
	if (copy_to_user(buf, buffer, len) != 0) {
		return -EFAULT;
	}
	return len;
}

static ssize_t demo_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
	if (copy_from_user(buffer, buf, len) != 0) {
		return -EFAULT;
	}
	return len;
}

static struct file_operations fops = {
	.owner = THIS_MODULE,
	.open = demo_open,
	.release = demo_release,
	.read = demo_read,
	.write = demo_write,
};


static const struct of_device_id demo_of_match[] = {
	{ .compatible = "arm,mali-mve", },
	{}
};

MODULE_DEVICE_TABLE(of, demo_of_match);

static int demo_probe(struct platform_device *pdev)
{
	int private_data = 0;

	/* If not mknod
	 * major: 0 or auto
	 * minor: by system, cat /proc/devices, on 1st column
	 * mknod /dev/xxx c <major> <minor>
	 * */

	cdev_major = register_chrdev(0, DEMO_CDEV_NAME, &fops);
	if (cdev_major < 0) {
		printk(KERN_ERR "Failed to register character device\n");
		return cdev_major;
	}

	/* create : /sys/class/<class_name> */
	demo_class = class_create(THIS_MODULE, DEMO_CLASS_NAME);

	/* create : /dev/<device_name>, /sys/class/<class_name>/<device_name> */
	demo_device = device_create(demo_class, NULL, MKDEV(cdev_major, 0), NULL, DEMO_DEVICE_NAME);

	printk(KERN_INFO "Platform device probed\n");

	platform_set_drvdata(pdev, (void *)&private_data);

	return 0;
}

static int demo_remove(struct platform_device *pdev) {
	device_destroy(demo_class, MKDEV(cdev_major, 0));

	class_destroy(demo_class);

	unregister_chrdev(cdev_major, DEMO_CDEV_NAME);

	printk(KERN_INFO "Platform device removed\n");
	return 0;
}

static struct platform_driver demo_plat_driver = {
	.driver = {
		.name = DEMO_DRIVER_NAME,
		.of_match_table = demo_of_match,
	},
	.probe = demo_probe,
	.remove = demo_remove,
};

module_platform_driver(demo_plat_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("WHO");
MODULE_DESCRIPTION("Platform Driver DEMO");
MODULE_ALIAS("platform:" DEMO_DRIVER_NAME);
